#include "work.hpp"
#include <string>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>

enum
{
    UsageError = 1,
    ArgError,
    PipeError
};


class Channel //管道/信号通道
{
public:                                                                                                                                                
    Channel(const string& name, const int& wfd, const int& subprocess_id): _name(name), _wfd(wfd), _subprocess_id(subprocess_id)
    {
    }

    void debugPrintChannel()
    {
        cout << "name:" << _name << ", wfd:" << _wfd << ", subprocess_id:" << _subprocess_id << endl; 
    }

    int getWfd()
    {
        return _wfd;
    }

    int getSubprocessId()
    {
        return _subprocess_id;
    }

    void closeChannel() //关闭通道的写
    {
        cout << "close " << _name << "'s w, subprocess is " << _subprocess_id << endl;
        close(_wfd);        
    }

private:
    string _name;
    int _wfd;
    int _subprocess_id;
};


class Processpool //进程池
{
public:
    Processpool(int num = 5): _num(num)
    {
    }
                                                                                                                                                       
    int creatProcesspool(work_p work)
    {
        vector<int> fds;
        //创建子进程和管道，并将信息存放到_channelpool
        for(int i = 0; i < _num; i++)
        {
            int pipefd[2];
            int ret = pipe(pipefd); //分别以读和写的方式打开文件，并将文件描述符放到输出型参数中，成功返回0，失败返回-1并设置错误码
            if(ret == -1)
                return PipeError;
            pid_t id = fork();
            if(id == 0)
            {
                //child
                if(!fds.empty())
                {
                    cout << "close fd: ";
                    for(auto e : fds)
                    {
                        cout << e << " ";
                        close(e);
                    }
                    cout << endl;
                }
                close(pipefd[1]); //子进程读
                
                //dup2(pipefd[0], 0); //本来是从pipefd[0]中读取，但是读取要实现在work()中，由于不想传参，直接将标准输入覆盖，work中直接就从0中读取，相当>于从pipefd[0]读取

                work(pipefd[0]);
                exit(0);
            }
            //father
            close(pipefd[0]); //父进程写

            string name = "channel-" + to_string(i);
            _channelpool.push_back(Channel(name, pipefd[1], id));

            //父进程的写端保存
            sleep(1);
            fds.push_back(pipefd[1]);
        }
    }

    void debugPrintProcesspool()
    {
        for(vector<Channel>::iterator it = _channelpool.begin(); it != _channelpool.end(); it++)
        {                                                                                                                                              
            it->debugPrintChannel();
        }
    }

    int nextChannel()
    {
        static int cnt = 0;
        int ret = cnt;
        cnt = (cnt + 1) % _channelpool.size();
        return ret;
    }

    void sendTaskToChannel(int& channelcode, int& taskcode)
    {
        write(_channelpool[channelcode].getWfd(), &taskcode, sizeof(taskcode));
        sleep(1);
    }

    void kill() //退出所有子进程
    {
        for(auto& e : _channelpool)
        {
            e.closeChannel();
            pid_t pid = e.getSubprocessId();

            //关闭一个管道的写端，就等待回收对应的子进程，这种写法有问题，因为继承导致管道的写端不止一个
            //第一次继承，子进程对应的管道写端是最多的，最有一次继承，子进程对应的管道写端只有一个
            // 解决方法：
            //a：混合写，逆向等待，先等待最后一个子进程，因为最后一个子进程释放，倒数第二个管道写端也就释放完了，依次类推
            //b：分开写，采用下面函数waitSubprocess
            //c：前两种都没有从根源上解决问题，我们一开始就应该让每个管道只有一个写端，后面就不会有问题，在creatProcesspool方法中改进
            // pid_t rid = waitpid(pid, nullptr, 0);
            // if(rid == pid)
            //     cout << "wait subprocess " << pid << " success" << endl;
        }
    }

    void waitSubprocess()
    {
        for(auto& e : _channelpool)
        {
            pid_t pid = e.getSubprocessId();
            pid_t rid = waitpid(pid, nullptr, 0);
            if(rid == pid)
                cout << "wait subprocess " << pid << " success" << endl;
        }
    }

private:
    int _num;
    vector<Channel> _channelpool; //存放管道的数组
};

int main(int argc, char* argv[])
{                                                                                                                                                      
    if(argc != 2)
    {
        cout << "参数个数错误！" << endl;
        return ArgError;
    }
    
    int subprocess_num = atoi(argv[1]);
    if(subprocess_num < 0)
    {
        cout << "子进程个数错误！" << endl;
        return PipeError;
    }

    srand((unsigned int)time(NULL));

    Processpool propool(subprocess_num); //创建一个进程池(默认有5个子进程)

    //1、父进程创建子进程和管道                                                                                                                        
    propool.creatProcesspool(work);
    propool.debugPrintProcesspool();

    //2、父进程控制子进程(子进程要负载均衡)
    int cnt = 10; //控制次数
    while(cnt)
    {
        cnt--;
        //a：选择管道和子进程
        int channelcode = propool.nextChannel();
        //b：选择任务
        int taskcode = randTask();
        //c：发送任务
        propool.sendTaskToChannel(channelcode, taskcode);
        sleep(1);
    }

    //3、回收子进程
    //a、关闭所有的写端，让子进程退出
    propool.kill();

    //b、等待子进程，回收资源，如果父进程不回收，就会被父进程的父进程领养
    propool.waitSubprocess();

    return 0;
}
